package de.fhin.mas.agents.manager.behaviours;

import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.FSMBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.domain.FIPANames;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import de.fhin.mas.MASEvents;
import de.fhin.mas.agents.manager.ManagerAgent;
import de.fhin.mas.ontology.SearchableObject;
import de.fhin.mas.ontology.Location;
import de.fhin.mas.util.MessageUtil;

/**
 * Initiierung eines neuen Auftrags + darstellen des Auftragslebenszyklus als
 * Zustandsautomat.
 * 
 * @author Ast
 * 
 */
@SuppressWarnings("serial")
public class InitNewOrder extends FSMBehaviour {
	private static final long serialVersionUID = -599577781938263072L;
	private final ManagerAgent managerAgent;
	private Location target;
	private SearchableObject object;

	/**
	 * Zustand "new_order_call"
	 * <p>
	 * Ereignis: init_new_order-REQUEST
	 * <p>
	 * Ausgabe: new_order-CFP
	 */
	private Behaviour sendNewOrderCallBv = new Behaviour(myAgent) {
		private Integer doTransition = null;

		@Override
		public void action() {
			managerAgent.logInfo("sendNewOrderCallBv.action()");

			// "newOrder" Message = CFP Message
			ACLMessage msg = new ACLMessage(ACLMessage.CFP);
			managerAgent.fillMsgContent(msg, object, target);
			msg.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);
			MessageUtil.setMessageTopic(managerAgent, msg, MASEvents.NEW_ORDER);

			// Abschicken der Nachricht
			managerAgent.sendToAll(msg);
			managerAgent.logInfo("new_order: CFP sent.");

			// Auf in den nächsten Zustand!
			doTransition = 0;
		}

		@Override
		public int onEnd() {
			int result = doTransition;
			doTransition = null;
			return result;
		}

		@Override
		public boolean done() {
			return doTransition != null;
		}
	};

	/**
	 * Zustand: Warte auf Signal von den Agenten, dass diese die Auftrag
	 * durchführen könne (PROPOSE).
	 */
	private Behaviour waitForProposalsBv = new Behaviour(myAgent) {
		private int receivedProposes = 0;
		private Integer doTransition = null;

		// private final Date startTime = new Date();

		@Override
		public void action() {
			managerAgent.logInfo("waitForProposalsBv.action()");

			MessageTemplate mt = MessageTemplate.and(MessageTemplate
					.MatchConversationId(MASEvents.NEW_ORDER.getName()),
					MessageTemplate.or(MessageTemplate
							.MatchPerformative(ACLMessage.PROPOSE),
							MessageTemplate
									.MatchPerformative(ACLMessage.REFUSE)));

			// für Testzwecke ohne Timeout!
			/*
			 * if (new Date().getTime() - startTime.getTime() > 20000) {
			 * doTransition = 0; System.out.println("Timeout"); }
			 */

			ACLMessage msg = myAgent.receive(mt);
			if (msg == null)
				block();
			else {
				if (msg.getPerformative() == ACLMessage.PROPOSE) {
					managerAgent.logInfo("Propose received from "
							+ msg.getSender().getName());
					receivedProposes++;
					if (receivedProposes >= 3)
						doTransition = 1;
				} else if (msg.getPerformative() == ACLMessage.REFUSE)
					doTransition = 0;
			}
		}

		@Override
		public boolean done() {
			return doTransition != null;
		}

		@Override
		public int onEnd() {
			if (doTransition == 0) {
				managerAgent.logInfo("new_order: Transition to order_canceled");

				ACLMessage msg = new ACLMessage(ACLMessage.REJECT_PROPOSAL);
				MessageUtil.setMessageTopic(managerAgent, msg,
						MASEvents.NEW_ORDER);
				msg
						.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);

				managerAgent.sendToAll(msg);

			} else if (doTransition == 1) {
				managerAgent.logInfo("new_order: Transition to order_approved");

				ACLMessage msg = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
				managerAgent.fillMsgContent(msg, object, target);
				MessageUtil.setMessageTopic(managerAgent, msg,
						MASEvents.NEW_ORDER);
				msg
						.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);

				managerAgent.sendToAll(msg);
			}
			return doTransition;
		}
	};

	private Behaviour orderExecutedBv = new OneShotBehaviour(myAgent) {
		private static final long serialVersionUID = 1L;

		@Override
		public void action() {
			managerAgent.logInfo("orderExecutedBv.action()");
		}
	};

	private Behaviour orderApprovedBv = new Behaviour(myAgent) {
		private Integer doTransition = null;

		@Override
		public void action() {
			managerAgent.logInfo("orderApprovedBv.action()");

			MessageTemplate mt = MessageTemplate.and(MessageTemplate
					.MatchConversationId(MASEvents.NEW_ORDER.getName()),
					MessageTemplate.or(MessageTemplate
							.MatchPerformative(ACLMessage.FAILURE),
							MessageTemplate
									.MatchPerformative(ACLMessage.INFORM)));

			while (true) {
				ACLMessage msg = myAgent.receive(mt);
				if (msg == null)
					block();

				else {
					if (msg.getPerformative() == ACLMessage.FAILURE)
						doTransition = 0;
					else if (msg.getPerformative() == ACLMessage.INFORM)
						doTransition = 1;
				}
				if (done())
					break;
			}
		}

		@Override
		public boolean done() {
			return doTransition != null;
		}

		@Override
		public int onEnd() {
			// TODO: Auftrag bei allen abbrechen
			if (doTransition == 0) {
				managerAgent.logInfo("new_order: Transition to order_failed");
			} else if (doTransition == 1) {
				managerAgent.logInfo("new_order: Transition to order_executed");
			}

			int result = doTransition;
			doTransition = null;
			return result;
		}
	};

	private Behaviour orderFailedBv = new OneShotBehaviour(myAgent) {
		private static final long serialVersionUID = -1623562850922633095L;

		@Override
		public void action() {
			managerAgent.logInfo("orderFailedBv.action()");
		}
	};

	private void setupFsm() {
		registerFirstState(sendNewOrderCallBv, "neworder_init");
		registerState(waitForProposalsBv, "wait_for_proposals");
		registerState(orderApprovedBv, "order_approved");
		registerLastState(orderFailedBv, "order_failed");
		registerLastState(orderExecutedBv, "order_executed");

		registerTransition("neworder_init", "wait_for_proposals", 0);
		registerTransition("wait_for_proposals", "order_failed", 0);
		registerTransition("wait_for_proposals", "order_approved", 1);
		registerTransition("order_approved", "order_failed", 0);
		registerTransition("order_approved", "order_executed", 1);
	}

	@Override
	public void reset() {
		super.reset();
		setupFsm();
	}

	public InitNewOrder(Agent a, SearchableObject object, Location target) {
		super(a);

		this.managerAgent = (ManagerAgent) a;
		this.object = object;
		this.target = target;

		setupFsm();
	}

}
